#pragma once

#include "vec2i.h"
#include <math.h>

OPEN_JLIB_NS


struct alignas(8) Vec2 {

  f32 p[2];

  inline Vec2 () noexcept {
  }
  inline Vec2 (Vec2i v) noexcept {
    p[0] = v.x();
    p[1] = v.y();
  }
  inline Vec2 (f32 x, f32 y) noexcept {
    p[0] = x;
    p[1] = y;
  }
  inline Vec2 (f32 s) noexcept {
    p[0] = s;
    p[1] = s;
  }
  inline Vec2 (const f32* m) noexcept {
    p[0] = m[0];
    p[1] = m[1];
  }


  inline f32 x () const noexcept {
    return p[0];
  }
  inline f32 y () const noexcept {
    return p[1];
  }


  inline Vec2& x (f32 s) noexcept {
    p[0] = s;
    return *this;
  }
  inline Vec2& y (f32 s) noexcept {
    p[1] = s;
    return *this;
  }


  inline Vec2& operator+= (Vec2 v) noexcept {
    p[0] += v.p[0];
    p[1] += v.p[1];
    return *this;
  }
  inline Vec2& operator-= (Vec2 v) noexcept {
    p[0] -= v.p[0];
    p[1] -= v.p[1];
    return *this;
  }
  inline Vec2& operator*= (Vec2 v) noexcept {
    p[0] *= v.p[0];
    p[1] *= v.p[1];
    return *this;
  }
  inline Vec2& operator/= (Vec2 v) noexcept {
    p[0] /= v.p[0];
    p[1] /= v.p[1];
    return *this;
  }


  inline Vec2& operator*= (f32 s) noexcept {
    p[0] *= s;
    p[1] *= s;
    return *this;
  }
  inline Vec2& operator/= (f32 s) noexcept {
    p[0] /= s;
    p[1] /= s;
    return *this;
  }


  inline Vec2& clamp (Vec2 min, Vec2 max) noexcept {
    p[0] = fminf(fmaxf(p[0], min.p[0]), max.p[0]);
    p[1] = fminf(fmaxf(p[1], min.p[1]), max.p[1]);
    return *this;
  }
  inline f32 normalize () noexcept {
    auto l = sqrtf(p[0]*p[0] + p[1]*p[1]);
    p[0] /= l;
    p[1] /= l;
    return l;
  }


  inline Vec2 operator+ (Vec2 v) const noexcept {
    return {p[0]+v.p[0], p[1]+v.p[1]};
  }
  inline Vec2 operator- (Vec2 v) const noexcept {
    return {p[0]-v.p[0], p[1]-v.p[1]};
  }
  inline Vec2 operator* (Vec2 v) const noexcept {
    return {p[0]*v.p[0], p[1]*v.p[1]};
  }
  inline Vec2 operator/ (Vec2 v) const noexcept {
    return {p[0]/v.p[0], p[1]/v.p[1]};
  }


  inline Vec2 operator* (f32 s) const noexcept {
    return {p[0]*s, p[1]*s};
  }
  inline Vec2 operator/ (f32 s) const noexcept {
    return {p[0]/s, p[1]/s};
  }


  inline f32 operator& (Vec2 v) const noexcept {
    return p[0]*v.p[0] + p[1]*v.p[1];
  }
  inline f32 operator^ (Vec2 v) const noexcept {
    return p[0]*v.p[1] - p[1]*v.p[0];
  }


  inline Vec2 operator+ () const noexcept {
    return {fabsf(p[0]), fabsf(p[1])};
  }
  inline Vec2 operator- () const noexcept {
    return {-p[0], -p[1]};
  }
  inline Vec2 operator* () const noexcept {
    auto l = sqrtf(p[0]*p[0] + p[1]*p[1]);
    return {p[0]/l, p[1]/l};
  }
  inline f32 operator~ () const noexcept {
    return sqrtf(p[0]*p[0] + p[1]*p[1]);
  }


  inline static Vec2 zero () noexcept {
    return {0.0F, 0.0F};
  }
  inline static Vec2 one () noexcept {
    return {1.0F, 1.0F};
  }
  template<u8 Index>
  inline static Vec2 unit () noexcept;


  inline static f32 distance (Vec2 a, Vec2 b) noexcept {
    return ~(b-a);
  }
  inline static Vec2 min (Vec2 a, Vec2 b) noexcept {
    return {
      fminf(a.p[0], b.p[0]),
      fminf(a.p[1], b.p[1]),
    };
  }
  inline static Vec2 max (Vec2 a, Vec2 b) noexcept {
    return {
      fmaxf(a.p[0], b.p[0]),
      fmaxf(a.p[1], b.p[1]),
    };
  }
  inline static Vec2 clamp (Vec2 v, Vec2 min, Vec2 max) noexcept {
    return Vec2::min(Vec2::max(v, min), max);
  }
  inline static Vec2 lerp (Vec2 a, Vec2 b, Vec2 t) noexcept {
    return a + (b-a)*t;
  }
  inline static Vec2 slerp (Vec2 a, Vec2 b, f32 t) noexcept {
    auto La = a.normalize();
    auto Lb = b.normalize();
    auto Lt = La + (Lb-La)*t;
    auto Ln = a ^ b;
    auto R = asinf(Ln);
    auto r = R * t;
    auto p = sinf(R-r) / Ln;
    auto q = sinf(r) / Ln;
    return (a*p + b*q) * Lt;
  }
};


template<>
inline Vec2 Vec2::unit<0> () noexcept {
  return {1.0F, 0.0F};
}
template<>
inline Vec2 Vec2::unit<1> () noexcept {
  return {0.0F, 1.0F};
}


inline Vec2 operator* (f32 a, Vec2 b) noexcept {
  return b * a;
}


CLOSE_JLIB_NS
